// Copyright (c) 2006-2008 Simon Fell
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
// THE SOFTWARE.
//

#import "zkBaseClient.h"
#import "zkSoapException.h"
#import "zkParser.h"
#import "CallBackData.h"

@implementation ZKBaseClient

static NSString *SOAP_NS = @"http://schemas.xmlsoap.org/soap/envelope/";
NSString * const TEXTXML_CONTENTTYPE = @"text/xml";

@synthesize httpResponse;
@synthesize mySelector;
@synthesize receivedData;
@synthesize connectionStack;

- (id) init {
	connectionStack = [[NSMutableDictionary alloc] initWithCapacity:3];
	return self;
}
- (void)dealloc {
	[endpointUrl release]; 
	[super dealloc];
}
- (NSMutableURLRequest *)makeRequest:(NSString *)payload {
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:endpointUrl]];
	[request setHTTPMethod:@"POST"];
	[request addValue:@"text/xml; charset=UTF-8" forHTTPHeaderField:@"content-type"];	
	[request addValue:@"\"\"" forHTTPHeaderField:@"SOAPAction"];
	
	NSData *data = [payload dataUsingEncoding:NSUTF8StringEncoding];
	[request setHTTPBody:data];
	return request;
}

- (zkElement *)sendRequest:(NSString *)payload {
	NSMutableURLRequest *request = [self makeRequest:payload];
	
	NSHTTPURLResponse *resp = nil;
	NSError *err = nil;
	// todo, support request compression
	// todo, support response compression
	NSData *respPayload = [NSURLConnection sendSynchronousRequest:request returningResponse:&resp error:&err];
	zkElement *body = [self processResponse:respPayload response:resp error:&err];
	return body;
}

- (void) sendRequestAsync:(NSString *)payload 
		withFirstDelegate:				(id)firstDelegate 
		withFirstSelector:				(NSString *)firstDelegateSelector 
	   withSecondDelegate:				(id)secondDelegate 
		withSecondDelegateSelector:	(NSString *)secondDelegateSelector  
		withErrorDelegate:				(id)errorDelegate 
		withErrorSelector:				(NSString *)errorDelegateSelector {
	
	NSMutableURLRequest *request = [self makeRequest:payload];
	
	receivedData = [[NSMutableData data] retain];
	
	NSURLConnection * conn = [NSURLConnection alloc];
	[conn initWithRequest:request delegate:self];
	
	CallBackData *cbd = [[CallBackData alloc] init];
	cbd.firstDelegate = firstDelegate;
	cbd.secondDelegate = secondDelegate;
	cbd.firstDelegateSelector = firstDelegateSelector;
	cbd.secondDelegateSelector = secondDelegateSelector;
	cbd.errorDelegate = errorDelegate;
	cbd.errorDelegateSelector = errorDelegateSelector;
	
	[connectionStack setObject:cbd forKey:[self getConnectionHashString:conn]];
	[cbd release];	
}

- (NSString *)getConnectionHashString:(NSURLConnection *)conn {
	NSNumber *hash = [NSNumber numberWithInt:[conn hash]];
	return [NSString stringWithFormat:@"%@", hash];
}

- (void)connection:(NSURLConnection *)conn didReceiveResponse:(NSURLResponse *)response
// A delegate method called by the NSURLConnection when the request/response 
// exchange is complete.  We look at the response to check that the HTTP 
// status code is 2xx and that the Content-Type is acceptable.  If these checks 
// fail, we give up on the transfer.
{
#pragma unused(conn)
    NSLog(@"didReceiveResponse");
    
    self.httpResponse = (NSHTTPURLResponse *) response;
    assert( [self.httpResponse isKindOfClass:[NSHTTPURLResponse class]] );
    
	if ((self.httpResponse.statusCode / 100) != 2) {
		NSLog(@"Bad status code.");
		//[self _stopReceiveWithStatus:[NSString stringWithFormat:@"HTTP error %zd", (ssize_t) httpResponse.statusCode] withConnection:conn];
    } else {
		NSString *  fileMIMEType;
        
       fileMIMEType = [[self.httpResponse MIMEType] lowercaseString];
		NSString *prefix = [NSString stringWithString:@"Mime type is: "];
		prefix = [prefix stringByAppendingString:fileMIMEType];
		
		if (fileMIMEType == nil) {
			NSLog(@"Bad MIME type.");
			[self _stopReceiveWithStatus:@"No Content-Type!" withConnection:conn];
		} else if ( ! [fileMIMEType isEqualToString:TEXTXML_CONTENTTYPE] ) {
			NSLog(@"Unsupported Content-Type");
			[self _stopReceiveWithStatus:[NSString stringWithFormat:@"Unsupported Content-Type (%@)", fileMIMEType] withConnection:conn];
       } else {
			NSLog(@"Response was OK.");
			//[self _updateStatus:@"Response OK."];
       }
    }    
}

- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data
// A delegate method called by the NSURLConnection as data arrives.  We just 
// write the data to the file.
{
	NSLog(@"Data Received.");

	//TODO call some event hook or a block method.
	[self.receivedData appendData:data];
}

- (void)connection:(NSURLConnection *)conn didFailWithError:(NSError *)error
// A delegate method called by the NSURLConnection if the connection fails. 
// We shut down the connection and display the failure.  Production quality code 
// would either display or log the actual error.
{
#pragma unused(error)
	
    NSLog(@"didFailWithError %@", error);

    [self _stopReceiveWithStatus:@"Connection failed" withConnection:conn];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)conn
// A delegate method called by the NSURLConnection when the connection has been 
// done successfully.  We shut down the connection with a nil status, which 
// causes the image to be displayed.
{
#pragma unused(conn)
	
	NSLog(@"connectionDidFinishLoading");
	id callBackDelegate;
	id inflightDelegate;
	
	NSError *err = nil;

	CallBackData *cbd = [connectionStack objectForKey:[self getConnectionHashString:conn]];
	if (cbd.firstDelegateSelector == nil && cbd.firstDelegate == nil && cbd.secondDelegate == nil && cbd.secondDelegateSelector == nil) {
		return;
	}
	
	@try {
		zkElement *resp = [self processResponse:receivedData response:self.httpResponse error:&err];
		
		SEL mycallback;
		mycallback = NSSelectorFromString(cbd.firstDelegateSelector);
		
		inflightDelegate = cbd.firstDelegate;
		
		if (cbd.secondDelegate == nil) {
			callBackDelegate = cbd.firstDelegate;
		} else {
			callBackDelegate = cbd.secondDelegate;
		}

		if ([inflightDelegate respondsToSelector:mycallback]) {
			NSLog(@"We can respond to the selector.");
			// This is an intermediate callback to the sforceClient
			[inflightDelegate performSelector:mycallback withObject:resp withObject:cbd];
		} else if ([callBackDelegate respondsToSelector:mycallback]) {
			// This is a callback to the user program
			[callBackDelegate performSelector:mycallback withObject:resp];
		} else {
			NSLog(@"We could not locate the selector.");
		}
		[resp retain];

	}
	@catch (ZKSoapException *e) {
		SEL mycallback;
		mycallback = NSSelectorFromString(cbd.errorDelegateSelector);

		if ([cbd.errorDelegate respondsToSelector:mycallback]) {
			NSLog(@"We can respond to the selector.");
			// This is the error handler on the user program
			[cbd.errorDelegate performSelector:mycallback withObject:e];
		}
	}
	@finally {
		[self _stopReceiveWithStatus:nil withConnection:conn];
	}

}

- (void)_stopReceiveWithStatus:(NSString *)statusString withConnection:(NSURLConnection *)conn
// Shuts down the connection and displays the result (statusString == nil) 
// or the error status (otherwise).
{
    if (conn != nil) {
        [conn cancel];
        conn = nil;
    }
    //[self _receiveDidStopWithStatus:statusString];
}



-(zkElement *)processResponse:(NSData *)respPayload response:(NSHTTPURLResponse *)resp error:(NSError **)err {
	zkElement *root = [zkParser parseData:respPayload];
	if (root == nil)	
		@throw [NSException exceptionWithName:@"Xml error" reason:@"Unable to parse XML returned by server" userInfo:nil];
	if (![[root name] isEqualToString:@"Envelope"])
		@throw [NSException exceptionWithName:@"Xml error" reason:[NSString stringWithFormat:@"response XML not valid SOAP, root element should be Envelope, but was %@", [root name]] userInfo:nil];
	if (![[root namespace] isEqualToString:SOAP_NS])
		@throw [NSException exceptionWithName:@"Xml error" reason:[NSString stringWithFormat:@"response XML not valid SOAP, root namespace should be %@ but was %@", SOAP_NS, [root namespace]] userInfo:nil];
	zkElement *body = [root childElement:@"Body" ns:SOAP_NS];
	if (500 == resp.statusCode) {
		zkElement *fault = [body childElement:@"Fault" ns:SOAP_NS];
		if (fault == nil)
			@throw [NSException exceptionWithName:@"Xml error" reason:@"Fault status code returned, but unable to find soap:Fault element" userInfo:nil];
		NSString *fc = [[fault childElement:@"faultcode"] stringValue];
		NSString *fm = [[fault childElement:@"faultstring"] stringValue];
		@throw [ZKSoapException exceptionWithFaultCode:fc faultString:fm];
	}
	return [[body childElements] objectAtIndex:0];
}

@end
